/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2010 Alexandre Croiseaux
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package marcel.core
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.Loader;
	import flash.media.Sound;
	import flash.net.URLVariables;
	import flash.system.ApplicationDomain;
	import flash.system.System;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	import marcel.debug.*;
	import marcel.display.gui.texts.StylesManager;
	import marcel.events.FileLoaderEvent;
	import marcel.external.FileLoader;
	import marcel.external.FileLoaderQueue;
	import marcel.external.FileType;
	import marcel.utils.BitmapUtils;
	import marcel.utils.ClassUtils;
	import marcel.utils.StringUtils;
	
	/**
	 * Class used to store all application assets
	 * @author Alexandre Croiseaux
	 */
	public class AssetManager extends FileLoaderQueue
	{
		//--------------------------------------------------------------------------
		//
		//  Private vars
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		protected var _dict:Dictionary = new Dictionary(true);
		
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		public function AssetManager()
		{
			addEventListener(FileLoaderEvent.COMPLETE, onFileLoaded);
			addEventListener(FileLoaderEvent.ERROR, onFileError);
		}
		
		//--------------------------------------------------------------------------
		//
		//  Public methods
		//
		//--------------------------------------------------------------------------
		/**
		 * Add a new asset identified by a string id
		 * @param	id	asset string id
		 * @param	asset	asset (xml, sound, image, swf...)
		 * @return	true is the asset has been successfully added, false if an asset with the same id already exists
		 */
		public function addAsset(id:String, asset:*):Boolean
		{
			if (hasAsset(id)) return false;
			_dict[id] = asset;
			return true;
		}
		
		/**
		 * Removes the asset identified by the passed id
		 * @param	id	asset id to remove
		 * @param	unloadIfLoader	Indicates if a unload action must be performed if the remove item is a Loader instance.
		 * @return	true if an asset with this id has been removed, false otherwise
		 * @see		flash.display.Loader
		 */
		public function removeAsset(id:String, unloadIfLoader:Boolean = true):Boolean
		{
			if (!hasAsset(id)) return false;
			if (_dict[id] is Loader)
			{
				_dict[id].unload();
				try { _dict[id].unloadAndStop(); }
				catch (e:Error) { };
			}
			else if (_dict[id] is XML)
			{
				try { System["disposeXML"](_dict[id]); } // a partir de player 10.1
				catch (e:Error) { };
			}
			_dict[id] = undefined;
			return true;
		}
		
		/**
		 * Removes all registered assets from the current manager
		 */
		public function clear():void
		{
			_dict = new Dictionary(true);
		}
		
		/**
		 * Checks if an asset with the specified id exists in the current asset manager
		 * @param	id	asset string id to test
		 * @return	true if an asset with this id exists, false otherwise
		 */
		public function hasAsset(id:String):Boolean
		{
			return (_dict[id] != undefined);
		}
		
		/**
		 * Returns the untyped asset identified by the id
		 * @param	id	asset string id
		 * @return	an untyped asset instance
		 */
		public function getAsset(id:String):* { return checkId(id); }
		
		/**
		 * Returns the asset identified by the id typed as Loader
		 * @param	id	asset string id
		 * @return	an Loader asset instance
		 */
		public function getLoaderAsset(id:String):Loader { return Loader(checkId(id)); }
		
		/**
		 * Returns the asset identified by the id typed as DisplayObject by calling Loader(asset_id).content
		 * @param	id	asset string id
		 * @return	an DisplayObject asset instance
		 */
		public function getSWFAsset(id:String):DisplayObject { return Loader(checkId(id)).content; }
		
		/**
		 * Returns the asset identified by the id typed as XML
		 * @param	id	asset string id
		 * @return	an XML asset instance
		 */
		public function getXMLAsset(id:String):XML { return XML(checkId(id)); }
		
		/**
		 * Returns the asset identified by the id typed as Sound
		 * @param	id	asset string id
		 * @return	an Sound asset instance
		 */
		public function getSoundAsset(id:String):Sound { return Sound(checkId(id)); }
		
		/**
		 * Returns the asset identified by the id typed as String
		 * @param	id	asset string id
		 * @return	an String asset instance
		 */
		public function getTextAsset(id:String):String { return String(checkId(id)); }
		
		/**
		 * Returns the asset identified by the id typed as ByteArray
		 * @param	id	asset string id
		 * @return	an String asset instance
		 */
		public function getBinaryAsset(id:String):ByteArray { return ByteArray(checkId(id)); }
		
		/**
		 * Returns the asset identified by the id typed as DisplayObject (only 1 instance exists for each load, use getBitmapAsset or getBitmapDataAsset to get copies of the image)
		 * @param	id	asset string id
		 * @return	an DisplayObject asset instance
		 */
		public function getImageAsset(id:String):DisplayObject { return Loader(checkId(id)).content; }
		
		/**
		 * Returns the asset identified by the id typed as BitmapData, or a BitmapData having a the specified classname
		 * @param	id	asset string id, or a full qualified class name
		 * @param	transparent	catch DisplayObject transparency or not
		 * @param	fillColor	if transparent is false, replace transparent values by this color
		 * @return	an BitmapData asset instance
		 */
		public function getBitmapDataAsset(id:String, transparent:Boolean = false, fillColor:uint = 0x00FFFFFF):BitmapData
		{
			// if a class exists with this name, we return the bitmapdata instance directly
			try
			{
				var c:Class = getDefinitionByName(id) as Class;
				return new c(0, 0);
			}
			catch (e:Error) {}
			
			// els we search in the assets
			var img:DisplayObject = getImageAsset(id);
			return BitmapUtils.getBitmapData(img, transparent, fillColor);
		}
		
		/**
		 * Returns the asset identified by the id typed as Bitmap, or a Bitmap containing a BitmapData having a the specified classname
		 * @param	id	asset string id, or a full qualified class name
		 * @param	transparent	catch DisplayObject transparency or not
		 * @param	fillColor	if transparent is false, replace transparent values by this color
		 * @param	pixelSnapping	Whether or not the Bitmap object is snapped to the nearest pixel.
		 * @param	smoothing	Controls whether or not the bitmap is smoothed when scaled.
		 * @return	an Bitmap asset instance
		 */
		public function getBitmapAsset(id:String, transparent:Boolean = false, fillColor:uint = 0x00FFFFFF, pixelSnapping:String = "auto", smoothing:Boolean = false):Bitmap
		{
			var bd:BitmapData = getBitmapDataAsset(id, transparent, fillColor);
			return new Bitmap(bd, pixelSnapping, smoothing);
		}
		
		/**
		 * Returns the dictionnary instance containing all available assets
		 * @return
		 */
		public function getAssets():Dictionary
		{
			return _dict;
		}
		
		/**
		 * Returns an instance of the specified fullClassName, looking for the definition in the specified domain.
		 * @param	fullClassName	the full qualified class name to instanciate.
		 * @param	domain	the domain to look for the class definition; if null, the swf current application domain is used.
		 * @return	an instance of the fullClassName type
		 * @throws	Error if the class is not found.
		 */
		public function getClassInstance(fullClassName:String, args:Array = null, domain:ApplicationDomain = null):*
		{
			var c:Class;
			try
			{
				if (domain != null) c = domain.getDefinition(fullClassName) as Class;
				else c = getDefinitionByName(fullClassName) as Class;
			}
			catch (e:Error) { throw "[AssetManager] class '" + fullClassName + "' not found !"; }
			
			return ClassUtils.buildInstance(c, args);
		}
		
		/**
		 * Add a new file to load to the queue without loading it.
		 * @param	id		an id to identify the asset to load
		 * @param	fileType	the FileType of the asset to load
		 * @param	fileURI		The URI of the data to load
		 * @param	variables	The urlVariables instance used to load assets. default is null.
		 * @see 	marcel.external.FileType
		 * @return	The FileLoader instance that has been created and pushed into the queue
		 */
		override public function enqueue(id:String, fileType:FileType, fileURI:String, variables:URLVariables = null):FileLoader
		{
			if (hasAsset(id))
			{
				debugw("Asset with id '" + id + "' not queued => id already exists in asset manager, please use another id !");
				return null;
			}
			return super.enqueue(id, fileType, fileURI, variables);
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------
		private function checkId(id:String):*
		{
			if (!_dict[id]) throw "[AssetManager] asset with id '" + id + "' not found !";
			return _dict[id];
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Events
		//
		//--------------------------------------------------------------------------
		private function onFileLoaded(evt:FileLoaderEvent):void
		{
			var fl:FileLoader = evt.fileLoader;
			var added:Boolean = addAsset(fl.id, evt.content);
			if (added)
			{
				var strBytes:String = " bytes";
				var bytes:uint = fl.getBytesTotal();
				var strSize:String = "";
				if (fl.getFileType() == FileType.IMAGE)
				{
					var w:uint = evt.content.width;
					var h:uint = evt.content.height;
					var limit:Number = 1;
					strSize = ", " + w + "x" + h + "px";
					if (w * h / bytes < limit) debugw("Image '" + fl.id + "' is " + StringUtils.formatSize(bytes) + " for " + w + "x" + h + "px. Did you optimize it ?");
				}
				else if (fl.getFileType() == FileType.CSS) StylesManager.pushCSS(fl.id, evt.content);
				
				debugi("Asset with id '" + fl.id + "' added! (type=" + fl.getFileType() + ", url=" + fl.fileUri + ", " + StringUtils.formatSize(bytes) + strSize + ")");
			}
			
			removeFromQueue(fl.id);
		}
		
		private function onFileError(evt:FileLoaderEvent):void
		{
			debuge("Error loading asset '" + evt.fileLoader.id + "' (" + evt.fileLoader.fileUri + ") : " + evt.text);
		}
	}
}